import fs from "fs";
import axios from "axios";
import crypto from "crypto";
import { nanoid } from "nanoid";
import { serial_no } from "@/config/constant";
import { PrismaClient } from "@prisma/client";
import { payStatusConversion } from "@/config/constant";

const prisma = new PrismaClient();

/** 微信相关方法 */
export class WeChat {
   /** 签名 */
   static Sign(params: WeChat.Sign) {
      let { method, mchid, url, data, certKeyPath, serialNo } = params;

      /** 时间戳 */
      const timestamp = Math.floor(new Date().getTime() / 1000);
      /** 随机字符串 */
      const nonceStr = nanoid(32);
      let str = "";

      if (method === "GET") {
         if (typeof data == "object") {
            let params: string = "";

            Object.keys(data).forEach((item, index) => {
               params += `${index === 0 ? "$" : "?"}${item}=${data[item]}`;
            });

            url += params;
         }
         str = `${method}\n${url}\n${timestamp}\n${nonceStr}\n\n`;
      } else {
         const body = JSON.stringify(data);
         str = `${method}\n${url}\n${timestamp}\n${nonceStr}\n${body}\n`;
      }

      /** 读取私钥 */
      const privateKey = crypto.createPrivateKey(fs.readFileSync(certKeyPath));
      /** 生成签名字符串 */
      const signature = crypto.sign("RSA-SHA256", Buffer.from(str), privateKey).toString("base64");
      /** 组合字符串 */
      const Authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",serial_no="${serialNo}",nonce_str="${nonceStr}",timestamp="${timestamp}",signature="${signature}"`;

      return Authorization;
   }

   /** 小程序端签名 */
   static SignMP(params: WeChat.SignMP) {
      const { prepay_id, certKeyPath } = params;
      /** 时间戳 */
      const timestamp = Math.floor(new Date().getTime() / 1000);
      /** 随机字符串 */
      const nonceStr = nanoid(32);
      /** 读取私钥 */
      const privateKey = crypto.createPrivateKey(fs.readFileSync(certKeyPath));
      /** 生成代签名字符串 */
      const str = `${process.env.APPID}\n${timestamp}\n${nonceStr}\nprepay_id=${prepay_id}\n`;
      /** 生成签名 */
      const signature = crypto.sign("RSA-SHA256", Buffer.from(str), privateKey).toString("base64");

      return {
         timestamp,
         nonceStr,
         signature
      };
   }

   /** 微信返回报文内容解密 */
   static Decrypt(params: WeChat.Decrypt) {
      const { APIV3, resource } = params;

      const AUTH_KEY_LENGTH = 16;
      // ciphertext = 密文，associated_data = 填充内容， nonce = 位移
      const { ciphertext, associated_data, nonce } = resource;
      // 密钥
      const key_bytes = Buffer.from(APIV3, "utf8");
      // 位移
      const nonce_bytes = Buffer.from(nonce, "utf8");
      // 填充内容
      const associated_data_bytes = Buffer.from(associated_data, "utf8");
      // 密文Buffer
      const ciphertext_bytes = Buffer.from(ciphertext, "base64");
      // 计算减去16位长度
      const cipherdata_length = ciphertext_bytes.length - AUTH_KEY_LENGTH;
      // upodata
      const cipherdata_bytes = Uint8Array.prototype.slice.call(ciphertext_bytes, 0, cipherdata_length);
      // tag
      const auth_tag_bytes = Uint8Array.prototype.slice.call(ciphertext_bytes, cipherdata_length, ciphertext_bytes.length);
      const decipher = crypto.createDecipheriv("aes-256-gcm", key_bytes, nonce_bytes);
      decipher.setAuthTag(auth_tag_bytes);
      decipher.setAAD(Buffer.from(associated_data_bytes));

      const output = Buffer.concat([decipher.update(cipherdata_bytes), decipher.final()]).toString();
      return output;
   }

   /** 从微信查询订单状态 */
   static async SearchOrderStatus(params: WeChat.SearchOrderStatus) {
      const { merchant_id, pay_id } = params;

      const merchant = await prisma.mp_merchant.findUnique({ where: { id: merchant_id } });
      if (!merchant) return null;

      const url = `/v3/pay/transactions/out-trade-no/${pay_id}?mchid=${merchant.code}`;
      const Authorization = this.Sign({
         url,
         method: "GET",
         mchid: merchant.code,
         certKeyPath: merchant.cert_key,
         serialNo: merchant.serial_no
      });
      /** 查询微信中当前订单状态 */
      const { data } = await axios.get<WeChat.ResourceDecrypt>("https://api.mch.weixin.qq.com" + url, {
         headers: { Authorization }
      });

      /** 更新状态并返回数据 */
      return await prisma.pay_info.update({
         where: { pay_id },
         data: {
            appid: data.appid,
            openid: data.payer?.openid || null,
            bank_type: data.bank_type || null,
            success_time: data.success_time || null,
            trade_type: data.trade_type || null,
            actual_expenses: data.amount?.payer_total || null,
            status: payStatusConversion[data.trade_state],
            wx_transaction_id: data.transaction_id || null
         }
      });
   }
}
